#include "frozen-bubble.h"

bool game_running = FALSE;

FBLevel *level;

int balls[GAME_ROWS][GAME_COLS];

FBImage *shooter_images[(GAME_SHOOTER_ROTATIONS * 2) + 1];
SDL_Rect shooter_rects[(GAME_SHOOTER_ROTATIONS * 2) + 1];
int shooter_angle;

int compressor_level;

void myLockSurface(SDL_Surface * s)
{
	while (SDL_MUSTLOCK(s) == 1 && SDL_LockSurface(s) < 0)
		SDL_Delay(10);
}

void myUnlockSurface(SDL_Surface * s)
{
	if (SDL_MUSTLOCK(s))
		SDL_UnlockSurface(s);
}

/* assumes the surface is not totally transparent */
void autopseudocrop (SDL_Surface *orig, SDL_Rect *rect)
{
	int x_ = -1, y_ = -1, x = 0, y = 0, w = -1, h = -1;
	Uint8 *ptr;
	int Adec = orig->format->Ashift / 8; // Adec is non standard from sdlpango_draw* output
	
	if (orig->format->BytesPerPixel != 4)
	{
		fprintf (logfile, "game: autocrop: orig surface must be 32bpp\n");
		exit (EXIT_FAILURE);
	}
	
	myLockSurface (orig);
	
	while (y_ == -1)
	{
		ptr = orig->pixels + y * orig->pitch;
		
		for (x = 0; x < orig->w; x++)
		{
			if (*(ptr + Adec) != 0)
			{
				y_ = y;
				break;
			}
			
			ptr += 4;
		}
		
		y++;
	}
	
	y = orig->h - 1;
	while (h == -1)
	{
		ptr = orig->pixels + y * orig->pitch;
		
		for (x = 0; x < orig->w; x++)
		{
			if (*(ptr + Adec) != 0)
			{
				h = y - y_ + 1;
				break;
			}
			
			ptr += 4;
		}
		
		y--;
	}
	
	x = 0;
	while (x_ == -1)
	{
		ptr = orig->pixels + x * 4;
		
		for (y = 0; y < orig->h; y++)
		{
			if (*(ptr + Adec) != 0)
			{
				x_ = x;
				break;
			}
			
			ptr += orig->pitch;
		}
		
		x++;
	}
	
	x = orig->w - 1;
	while (w == -1)
	{
		ptr = orig->pixels + x * 4;
		
		for (y = 0; y < orig->h; y++)
		{
			if (*(ptr + Adec) != 0)
			{
				w = x - x_ + 1;
				break;
			}
			
			ptr += orig->pitch;
		}
		
		x--;
	}
	
	myUnlockSurface (orig);
	
	rect->x = x_;
	rect->y = y_;
	rect->w = w;
	rect->h = h;
}

/* access interleaved pixels */
#define CUBIC_ROW(dx, row) transform_cubic(dx, (row)[0], (row)[4], (row)[8], (row)[12])

#define CUBIC_SCALED_ROW(dx, row, arow) transform_cubic(dx, (arow)[0] * (row)[0], (arow)[4] * (row)[4], (arow)[8] * (row)[8], (arow)[12] * (row)[12])

#define CLAMP(x, low, high)  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))

static inline double
transform_cubic(double dx, int jm1, int j, int jp1, int jp2)
{
        // http://news.povray.org/povray.binaries.tutorials/attachment/%3CXns91B880592482seed7@povray.org%3E/Splines.bas.txt
        // Catmull-Rom yields the best results
        return ((( (     - jm1 + 3 * j - 3 * jp1 + jp2 ) * dx +
                   (   2 * jm1 - 5 * j + 4 * jp1 - jp2 ) ) * dx +
                   (     - jm1             + jp1       ) ) * dx +
                   (             2 * j                 ) ) / 2.0;
}

void rotate_bicubic (SDL_Surface * dest, SDL_Surface * orig, double angle)
{
	int Bpp = dest->format->BytesPerPixel;
        Uint8 *ptr;
        int x_, y_, x, y;
        double cosval = cos(angle);
        double sinval = sin(angle);
        double a_val, a_recip;
        int   i;
        double dx, dy;
	if (orig->format->BytesPerPixel != 4) {
                fprintf(stderr, "rotate_bicubic: orig surface must be 32bpp (bytes per pixel = %d)\n", orig->format->BytesPerPixel);
                abort();
        }
	if (dest->format->BytesPerPixel != 4) {
                fprintf(stderr, "rotate_bicubic: dest surface must be 32bpp\n");
                abort();
        }
	myLockSurface(orig);
	myLockSurface(dest);
        for (y = 0; y < dest->h; y++) {
                double x__ = - dest->w/2*cosval - (y - dest->h/2)*sinval + dest->w/2 - 1;
                double y__ = (y - dest->h/2)*cosval - dest->w/2*sinval + dest->h/2 - 1;
                ptr = dest->pixels + y*dest->pitch;
                for (x = 0; x < dest->w; x++) {
                        x_ = floor(x__);
                        y_ = floor(y__);
                        if (x_ < 0 || x_ > orig->w - 4 || y_ < 0 || y_ > orig->h - 4) {
                                * ( (Uint32*) ptr ) = 0;

                        } else {
                                Uint8* origptr = orig->pixels + x_*Bpp + y_*orig->pitch;

                                /* the fractional error */
                                dx = x__ - x_;
                                dy = y__ - y_;
                                /* calculate alpha of result */
                                a_val = transform_cubic(dy,
                                                        CUBIC_ROW(dx, origptr + 3),
                                                        CUBIC_ROW(dx, origptr + 3 + dest->pitch),
                                                        CUBIC_ROW(dx, origptr + 3 + dest->pitch * 2),
                                                        CUBIC_ROW(dx, origptr + 3 + dest->pitch * 3));
                                if (a_val <= 0.0) {
                                        a_recip = 0.0; 
                                        *(ptr+3) = 0;
                                } else if (a_val > 255.0) {
                                        a_recip = 1.0 / a_val;
                                        *(ptr+3) = 255;
                                } else { 
                                        a_recip = 1.0 / a_val;
                                        *(ptr+3) = (int) a_val;
                                }
                                /* for RGB, result = bicubic (c * alpha) / bicubic (alpha) */
                                for (i = 0; i < 3; i++) { 
                                        int newval = a_recip * transform_cubic(dy,
                                                                               CUBIC_SCALED_ROW (dx, origptr + i,                   origptr + 3),
                                                                               CUBIC_SCALED_ROW (dx, origptr + i + dest->pitch,     origptr + 3 + dest->pitch),
                                                                               CUBIC_SCALED_ROW (dx, origptr + i + dest->pitch * 2, origptr + 3 + dest->pitch * 2),
                                                                               CUBIC_SCALED_ROW (dx, origptr + i + dest->pitch * 3, origptr + 3 + dest->pitch * 3));
                                        *(ptr+i) = CLAMP(newval, 0, 255);
                                }
                        }
                        x__ += cosval;
                        y__ += sinval;
                        ptr += 4;
		}
	}
	myUnlockSurface(orig);
	myUnlockSurface(dest);
}

void game_init_shooter ()
{
	FBImage *shooter = IMG_SHOOTER;
	FBImage *rotimg;
	SDL_Rect *croprect;
	SDL_Surface *replace;
	double angle;
	
	for (int i = -GAME_SHOOTER_ROTATIONS; i <= GAME_SHOOTER_ROTATIONS; i++)
	{
		angle = -i * (PI / 2) / GAME_SHOOTER_ROTATIONS;
		croprect = &shooter_rects[i + GAME_SHOOTER_ROTATIONS];
		
		assert (rotimg = get_image (NULL));
		assert (rotimg->surface = SDL_CreateRGBSurface (0, shooter->surface->w, shooter->surface->h, 32, RMASK, GMASK, BMASK, AMASK));
		
		rotate_bicubic (rotimg->surface, shooter->surface, angle);
		autopseudocrop (rotimg->surface, croprect);
		
		replace = SDL_CreateRGBSurface (0, croprect->w, croprect->h, 32, RMASK, GMASK, BMASK, AMASK);
		SDL_SetAlpha (rotimg->surface, 0, SDL_ALPHA_TRANSPARENT);
		SDL_BlitSurface (rotimg->surface, croprect, replace, NULL);
		SDL_FreeSurface (rotimg->surface);
		rotimg->surface = replace;
		rotimg->rect.x = 0;
		rotimg->rect.y = 0;
		rotimg->rect.w = croprect->w;
		rotimg->rect.h = croprect->h;
		
		shooter_images[i + GAME_SHOOTER_ROTATIONS] = rotimg;
	}
}

void game_render_balls (int balls[GAME_ROWS][GAME_COLS], int x, int y)
{
	int ball, i;
	FBImage *ballimg;
	char filename[50];
	
	for (i = 0; i < compressor_level; i++)
		put_image (IMG_COMPRESSOR_EXT, 225, -7 + (i * IMG_COMPRESSOR_EXT->surface->h));
	
	put_image (IMG_COMPRESSOR_MAIN, 192, -7 + (i * IMG_COMPRESSOR_EXT->surface->h));
	
	for (int row = 0; row < GAME_ROWS; row++)
	{
		for (int col = 0; col < GAME_COLS - (row % 2); col++)
		{
			ball = balls[row][col];
			
			if (ball < 0)
				continue;
			
			sprintf (filename, IMGTMPLT_BUBBLE, ball + 1);
			ballimg = get_image (filename);
			
			put_image (ballimg,
			           x + (col * ballimg->surface->w) + (row % 2 ? ballimg->surface->w / 2 : 0),
			           y + (row * ballimg->surface->h * 0.85)
			           );
		}
	}
}

void game_render_shooter (int angle, int x, int y)
{
	FBImage *img = shooter_images[angle + GAME_SHOOTER_ROTATIONS];
	
	put_image (img,
	           x + shooter_rects[angle + GAME_SHOOTER_ROTATIONS].x,
	           y + shooter_rects[angle + GAME_SHOOTER_ROTATIONS].y
	           );
}

void game_postrender (FBWidgetLayer *layer)
{
	game_render_balls (balls, 190, 44);
	game_render_shooter (shooter_angle, 268, 356);
}

void game_load_level (FBLevel *newlevel)
{
	level = newlevel;
	
	memset (balls, -1, sizeof (int) * GAME_ROWS * GAME_COLS);
	memcpy (balls, level->balls, sizeof (int) * LEVEL_ROWS * LEVEL_COLS);
	
	compressor_level = 0;
	shooter_angle = 0;
}

void game_start_1player ()
{
	FBWidgetLayer *layer = widget_layer_new (NULL);
	layer->on_postrender = game_postrender;
	
	layer->bg = widget_layer_adopt (layer, widget_new_image (IMG_BACK_ONE_PLAYER, 0, 0));
	
	widget_layer_root (layer);
	
	game_load_level (levelset->levels);
	
	game_running = TRUE;
}

void game_move_left ()
{
	if (--shooter_angle < -GAME_SHOOTER_ROTATIONS)
		shooter_angle = -GAME_SHOOTER_ROTATIONS;
}

void game_move_right ()
{
	if (++shooter_angle > GAME_SHOOTER_ROTATIONS)
		shooter_angle = GAME_SHOOTER_ROTATIONS;
}

void game_move_center ()
{
	if (shooter_angle < 0)
		game_move_right ();
	else if (shooter_angle > 0)
		game_move_left ();
}

